#include "TweakUtils/TweakPanel.hpp"
#include "TweakUtils/TweakUtils.hpp"
#include <string>
#include <locale>

#ifdef ENABLE_DEVBUILD

using namespace TweakUtils;


Panel::Panel( const std::string& name )
:	mName( name )
,	mR( 25 )
,	mG( 25 )
,	mB( 175 )
,	mA( 255 )
,	mTextColor( Panel::Light )
,	CONFIG_INIT( mShown, name+"/Shown", false )
,	CONFIG_INIT( mPosition, name+"/Position", Math::Point2i::Zero() )
,	CONFIG_INIT( mSize, name+"/Size", Math::Point2i( 50, 50 ) )
,	mpPanel( nullptr )
{
}


Panel::Panel( const Panel& rhs )
:	mName( rhs.mName )
,	mR( rhs.mR )
,	mG( rhs.mG )
,	mB( rhs.mB )
,	mA( rhs.mA )
,	mTextColor( rhs.mTextColor )
,	CONFIG_COPY_INIT( mShown, rhs )
,	CONFIG_COPY_INIT( mPosition, rhs )
,	CONFIG_COPY_INIT( mSize, rhs )
,	mpPanel( rhs.mpPanel )
,	mElements( rhs.mElements )
,	mpCallback( rhs.mpCallback )
{
	// This is not intended to be used on live objects.
	assert( mpPanel==nullptr );

	if( mShown )
		Show();

	Config::Instance->AddUpdater( this, [=](){Update();} );
}


Panel::~Panel()
{
	if( mpPanel )
		Hide();

	Config::Instance->RemoveUpdater( this );
}


Panel& Panel::operator =( const Panel& rhs )
{
	mName		=	rhs.mName;
	mR			=	rhs.mR;
	mG			=	rhs.mG;
	mB			=	rhs.mB;
	mA			=	rhs.mA;
	mTextColor	=	rhs.mTextColor;
	mShown		=	rhs.mShown;
	mPosition	=	rhs.mPosition;
	mSize		=	rhs.mSize;
	mpPanel		=	rhs.mpPanel;
	mElements	=	rhs.mElements;
	mpCallback	=	rhs.mpCallback;

	// This is not intended to be used on live objects.
	assert( mpPanel==nullptr );

	if( mShown )
		Show();

	Config::Instance->AddUpdater( this, [=](){Update();} );

	return *this;
}


void Panel::Show()
{
	assert( mpPanel==nullptr );
	_CreatePanel();

	// Update the tweakables from the new object.
	_UpdateTweakables();
}


void Panel::Hide()
{
	assert( mpPanel!=nullptr );

	// Update the tweakables from the object before it closes.
	_UpdateTweakables();

	TwDeleteBar( mpPanel );
	mpPanel		=	nullptr;
}


bool Panel::Toggle()
{
	if( mpPanel )
	{
		Hide();
	}
	else
	{
		Show();
	}

	mShown	=	mpPanel!=nullptr;
	return mShown;
}


void Panel::Update()
{
	if( mpCallback )
		mpCallback();

	for( auto it : mElements )
	{
		it->Update();
	}
}


//////////////////////////////////////////////////////////////////////////
Panel  Panel::Create( const std::string& name )
{
	return Panel( name );
}


Panel::EnumBuilder Panel::EnumType( const std::string name )
{
	return Panel::EnumBuilder( name );
}


//////////////////////////////////////////////////////////////////////////
Panel& Panel::Color( uint8_t r, uint8_t g, uint8_t b, uint8_t a )
{
	mR	=	r;
	mG	=	g;
	mB	=	b;
	mA	=	a;
	return *this;
}


Panel& Panel::Callback( Callback_t cb )
{
	mpCallback	=	cb;
	return *this;
}


Panel& Panel::DarkText()
{
	mTextColor	=	Dark;
	return *this;
}


Panel& Panel::LightText()
{
	mTextColor	=	Light;
	return *this;
}


//////////////////////////////////////////////////////////////////////////
Panel::String_t& Panel::String( const std::string& name, std::string* str )
{
	Panel::String_t*	result	=	new struct String_t( this, name, str );
	mElements.push_back( ElementPtr_t( result ) );
	return *result;
}


Panel::Color3f_t& Panel::Color3f( const std::string& name, Math::Vector3f* color )
{
	Panel::Color3f_t*	result	=	new struct Color3f_t( this, name, color );
	mElements.push_back( ElementPtr_t( result ) );
	return *result;
}


Panel::Separator_t& Panel::Separator( const std::string& name )
{
	Panel::Separator_t*	result	=	new struct Separator_t( this, name );
	mElements.push_back( ElementPtr_t( result ) );
	return *result;
}


Panel::Button_t& Panel::Button( const std::string& name, Callback_t cb )
{
	Panel::Button_t*	result	=	new struct Button_t( this, name, cb );
	mElements.push_back( ElementPtr_t( result ) );
	return *result;
}


Panel::Enumeration_t& Panel::Enumeration( const std::string& name, EnumType_t et, int32_t* value )
{
	Panel::Enumeration_t*	result	=	new struct Enumeration_t( this, name, et, value );
	mElements.push_back( ElementPtr_t( result ) );
	return *result;
}


Panel::Struct_t& Panel::Struct( const std::string& name, const StructType_t& type, void* data )
{
	Panel::Struct_t*	result	=	new struct Struct_t( this, name, type, data );
	mElements.push_back( ElementPtr_t( result ) );
	return *result;
}


//////////////////////////////////////////////////////////////////////////
void Panel::_CreatePanel()
{
	mpPanel		=	TwNewBar( _ToName( mName ).c_str() );
	
	for( ElementPtr_t element : mElements )
	{
		element->Create( *this );
	}

	TwDefine( (std::string( " " ) + _ToName( mName ) + " label='" + mName + "'"
		+ " color='" + std::to_string( mR ) + " " + std::to_string( mG ) + " " + std::to_string( mB ) + "'"
		+ " alpha=" + std::to_string( mA )
		).c_str() );
	TwDefine( (std::string( " " ) + _ToName( mName ) + " text=" + (mTextColor==Light ? "light" : "dark") ).c_str() );

	TwDefine( (std::string( " " ) + _ToName( mName ) + " position='" + std::to_string( mPosition( 0 ) ) + " " + std::to_string( mPosition( 1 ) ) + "'").c_str() );
	TwDefine( (std::string( " " ) + _ToName( mName ) + " size='" + std::to_string( mSize( 0 ) ) + " " + std::to_string( mSize( 1 ) ) + "'").c_str() );

	TwDefine( (std::string( " " ) + " GLOBAL contained=true").c_str() );
}


std::string Panel::_ToName( const std::string& label )
{
	// Remove white spaces from a label.
	std::locale	loc;
	std::string	result;
	for( char c : label )
	{
		if( std::isspace( c, loc ) )
			continue;
		result	+=	c;
	}

	return result;
}


TwBar* Panel::_Bar()
{
	return mpPanel;
}


void Panel::_UpdateTweakables()
{
	if( mpPanel )
	{
		{
			TwGetParam( mpPanel, NULL, "position", TW_PARAM_INT32, 2, &mPosition );
			TwGetParam( mpPanel, NULL, "size", TW_PARAM_INT32, 2, &mSize );
		}
	}
	else
	{
		LOG( INFO ) << "Shouldn't be updating tweakables with the panel closed.";
	}
}


//////////////////////////////////////////////////////////////////////////
Panel::Struct_t::Struct_t( Panel* owner, const std::string& name, const StructType_t& st, void* b )
:	Element_t( owner )
,	mName( name )
,	mType( st )
,	mpData( b )
{
}

void Panel::Struct_t::Create( Panel& p )
{
	TwAddVarRW( p._Bar(), mName.c_str(), mType.mEnumType, mpData, nullptr );
}


void Panel::Struct_t::Update()
{
}

Panel& Panel::Struct_t::End()
{
	return *mpOwner;
}


//////////////////////////////////////////////////////////////////////////
Panel::EnumBuilder::EnumBuilder( const std::string& name )
:	mName( name )
{
}


Panel::EnumBuilder::operator Panel::EnumType_t() const
{
	int32_t		value		=	~1;
	std::vector< TwEnumVal >	enumVals;

	for( const EnumEntry_t& it : mEntries )
	{
		switch( it.second )
		{
		case ~1:
			if( value==~1 )
				value	=	0;
			break;

		default:
			value	=	it.second;
			break;
		}
		TwEnumVal	entry	=	{value++, it.first.c_str()};
		enumVals.push_back( entry );
	}

	EnumType_t	result	=	{TwDefineEnum( ("Enum_" + mName).c_str(), enumVals.data(), enumVals.size() )};
	return result;
}


Panel::EnumBuilder& Panel::EnumBuilder::Entry( const std::string& name, uint32_t value )
{
	EnumEntry_t	entry( name, value );
	mEntries.push_back( entry );
	return *this;
}


//////////////////////////////////////////////////////////////////////////
Panel::String_t::String_t( Panel* owner, const std::string& name, std::string* str )
:	Element_t( owner )
,	mName( name )
,	mpString( str )
,	mOpened( false )
,	mLastValue( *str )
{
}


void Panel::String_t::Create( Panel& p )
{
	std::string	params;

	if( mOpened )	params	=	" opened=true";

	TwAddVarRW( p._Bar(), mName.c_str(), TwType::TW_TYPE_STDSTRING, mpString, params.empty() ? nullptr : params.c_str() );
}


void Panel::String_t::Update()
{
}


Panel::String_t& Panel::String_t::Callback( Callback_t cb )
{
	mpCallback	=	cb;
	return *this;
}


Panel::String_t& Panel::String_t::Opened()
{
	mOpened	=	true;
	return *this;
}


Panel& Panel::String_t::End()
{
	return *mpOwner;
}


//////////////////////////////////////////////////////////////////////////
Panel::Color3f_t::Color3f_t( Panel* owner, const std::string& name, Math::Vector3f* color )
:	Element_t( owner )
,	mName( name )
,	mpColor( color )
,	mOpened( false )
,	mLastValue( *color )
{
}


void Panel::Color3f_t::Create( Panel& p )
{
	std::string	params;

	if( mOpened )	params	=	" opened=true";

	TwAddVarRW( p._Bar(), mName.c_str(), TwType::TW_TYPE_COLOR3F, mpColor, params.empty() ? nullptr : params.c_str() );
}


void Panel::Color3f_t::Update()
{
}


Panel::Color3f_t& Panel::Color3f_t::Callback( Callback_t cb )
{
	mpCallback	=	cb;
	return *this;
}


Panel::Color3f_t& Panel::Color3f_t::Opened()
{
	mOpened	=	true;
	return *this;
}


Panel& Panel::Color3f_t::End()
{
	return *mpOwner;
}


//////////////////////////////////////////////////////////////////////////
Panel::Separator_t::Separator_t( Panel* owner, const std::string& name )
:	Element_t( owner )
,	mName( name )
{
}


void Panel::Separator_t::Create( Panel& p )
{
	TwAddSeparator( p._Bar(), mName.c_str(), nullptr );
}


Panel& Panel::Separator_t::End()
{
	return *mpOwner;
}


//////////////////////////////////////////////////////////////////////////
Panel::Button_t::Button_t( Panel* owner, const std::string& name, Callback_t cb )
:	Element_t( owner )
,	mName( name )
{
	mpCallback	=	cb;
}


void Panel::Button_t::Create( Panel& p )
{
	TwAddButton( p._Bar(), mName.c_str(), &Button_t::Callback, this, nullptr );
}


void Panel::Button_t::Update()
{
}


Panel& Panel::Button_t::End()
{
	return *mpOwner;
}

void Panel::Button_t::Callback( void* cbd )
{
	((Button_t*)cbd)->mpCallback();
}


//////////////////////////////////////////////////////////////////////////
Panel::Enumeration_t::Enumeration_t( Panel* owner, const std::string& name, EnumType_t et, int32_t* value )
:	Element_t( owner )
,	mName( name )
,	mEnumType( et )
,	mpValue( value )
,	mLastValue( *value )
{
}

void Panel::Enumeration_t::Create( Panel& p )
{
	TwAddVarRW( p._Bar(), mName.c_str(), mEnumType, mpValue, nullptr );
}


void Panel::Enumeration_t::Update()
{
	if( mpValue && (*mpValue != mLastValue ) )
	{
		mLastValue	=	*mpValue;
		
		if( mpCallback )
			(mpCallback)();
	}
}


Panel::Enumeration_t& Panel::Enumeration_t::Callback( Callback_t cb )
{
	mpCallback	=	cb;
	return *this;
}


Panel& Panel::Enumeration_t::End()
{
	return *mpOwner;
}


#endif // ENABLE_DEVBUILD
